package Master;

import java.io.IOException;
import java.net.ServerSocket;
import java.net.Socket;
import java.rmi.RemoteException;
import java.rmi.registry.LocateRegistry;
import java.rmi.registry.Registry;
import java.rmi.server.UnicastRemoteObject;
import java.util.HashSet;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;

import Interfaces.Task;
import Interfaces.WorkerInterface;
import Interfaces.WorkerRegister;


public class Master implements WorkerRegister {
	
	/**
	 * 
	 *	This class encapsulates a worker.
	 *
	 *	For a specific worker we save:
	 *		1. offered resources
	 *		2. the current number of jobs executed on it
	 *		3. the computer hostname for sending instructions to the worker
	 */
	class Worker {
		
		Set<String> resources = new HashSet<String>();
		// the current number of jobs executing on the worker
		int numJobs;
		
		public Worker(Set<String> resources) {
			this.resources.addAll(resources);
			this.numJobs=0;
		}
		public void increaseJobNum(){
			numJobs++;
		}
		public void decreaseJobNum(){
			numJobs--;
		}		
	}
	
	// we identify a worker by a special string created by the UUID class 
	private static Map<String,Worker> 	workersMap = 
										new ConcurrentHashMap<String,Worker>();

	/**
	 * Checks if the specified worker is suitable for the execution of task
	 * @param task the client sent for execution
	 * @param worker to query
	 * @return true if the worker has the resources the client specified in task
	 */
	private static boolean isWorkerSuitable(Task task, Worker worker) {
		for(String req : task.getRequirements()){
			if(!worker.resources.contains(req)) {
				return false;
			}
		}
		return true;
	}
	
	/**
	 * Searching for the worker that can run the task and has the minimal number
	 * of jobs.
	 * @param task the client sent
	 * @return the tuple in workersMap that corresponds to the found worker, or 
	 * null if no suitable worker is found.
	 */
	private static Entry<String,Worker> findWorker(Task task) {
		
		Entry<String,Worker> chosenWorker = null;
		int minJobs=Integer.MAX_VALUE;
		
		// searching for the worker that can run the task and has the minimal 
		// number of jobs
		
		for (Entry<String,Worker> workerTuple: workersMap.entrySet()) {
			
			Worker worker = workerTuple.getValue(); 
			if( isWorkerSuitable(task, worker) && (minJobs > worker.numJobs) ) {
				minJobs = worker.numJobs;
				chosenWorker = workerTuple;
			}
		}
		
		return chosenWorker;
	}
	
	/**
	 * This method tries to perform the task specified by the client. 
	 * We search for the appropriate worker (amongst all are signed ones), and
	 * send him the task for execution
	 * @param task the client sent
	 * @return true if we found a suitable worker and we were able to run it 
	 */
	public static boolean doTask(Task task){ // TODO maybe check if it should be a synchronized function
		
		Entry<String,Worker> chosenWorker = findWorker(task);
		
		if(chosenWorker == null) {
			return false;
		}
		
		Worker worker = chosenWorker.getValue();
		String workerID = chosenWorker.getKey();
		
		try {

			// getting the remote stub
			Registry registry = LocateRegistry.getRegistry();
			WorkerInterface remoteWorker=(WorkerInterface)registry.lookup(workerID);
			// performing the task and increasing the number of jobs run by worker
			worker.increaseJobNum();
			remoteWorker.doTask(task);
			// removing this completed job form the workers job count
			worker.decreaseJobNum();
			
		} catch (Exception e){
			workersMap.remove(workerID);
			return doTask(task);
		}
		
		return true;
		
	}
	
	@Override
	public void register(String name, Set<String> resources)
														throws RemoteException {
		workersMap.put(name, new Worker(resources));
	}
	
    public static void main(String[] args) {
    	
    	// acquiring the security policy
        if (System.getSecurityManager() == null) {
            System.setSecurityManager(new SecurityManager());
        }
        

        // registering the workers
        try {
        	
            String name = "Register";
            WorkerRegister workerRegistrer = new Master();
            WorkerRegister stub = (WorkerRegister) UnicastRemoteObject.exportObject(workerRegistrer , 0);
            Registry registry = LocateRegistry.getRegistry();
            // installing a special remote object to the local rmiregistry, this
            // object is responsible for the registration of the workers
            registry.rebind(name, stub);
            
        } catch (Exception e) {
        	
            e.printStackTrace();
            System.exit(-1);
        }
        
        ServerSocket srv=null;
        // acting as a server for the clients task requests
        try {
            int port = Integer.valueOf(args[0]);
            srv = new ServerSocket(port);
        } catch (IOException e) {
        	e.printStackTrace();
        	System.exit(-1);
        }
        
        while(true){ // TODO check if we really need to do a while true loop or is there some other way we need to operate 
        	try {
				Socket socket = srv.accept();
				// for each connection from the client we open new thread to handle it
                ClientServiceThread cliThread = new ClientServiceThread(socket); 
                cliThread.start();
			} catch (Exception e) {
				e.printStackTrace();
			}
        }
    } // main end

}